#import <QuartzCore/QuartzCore.h>
#import "ALTimelineScale.h"
#import "ALTimelineView.h"
#import "ALTimelineElement.h"
#import "ALTimelineCanvas.h"
#import "ALTimelineScaleLabel.h"
#import "NSObject+BlockObservation.h"
#import "NSDate+SMRAdditions.h"
#import "NSShadow+MCAdditions.h"


#pragma mark Constants
static const CGFloat ALTimelineScaleBorderThickness = 2.0f;
static const CGFloat ALTimelineScaleOptimumLabelSeparation = 250.0f;
static NSString* const ALTimelineScaleLabelToRemoveKey = @"ALTimelineScaleLabelToRemoveKey";
static NSString* const ALScaleTotalDaysKey = @"totalDays";
static NSString* const ALScaleAlphaKey = @"alphaValue";


#pragma mark -
#pragma mark Class Globals
static NSColor *ALTimelineScaleBackgroundColor;
static NSColor *ALTimelineScaleBorderColor;
static NSShadow *ALTimelineScaleBorderShadow;


#pragma mark -
@interface ALTimelineScale ()
//@property (nonatomic, assign) CGFloat pointsPerDay;
@property (nonatomic, retain) NSArray *labels;
@property (nonatomic, retain) NSArray *addedLabels;
@property (nonatomic, retain) NSArray *removedLabels;
@property (nonatomic, retain) NSArray *visibleLabels;
@end


#pragma mark -
@interface ALTimelineScale (ALTimelineScalePrivateMethods)
//-(void)updatePointsPerDay;
-(NSDate *)firstLabelDateWithInterval_:(NSDateComponents *)interval;
-(void)updatePositionOfLabel_:(ALTimelineScaleLabel *)label;
-(void)updateVisibleLabels_;
-(void)positionOldVisibleLabels_:(NSArray *)oldLabels;
-(void)positionAddedLabels_;
-(void)positionRemovedLabels_;
-(void)positionVisibleLabels_;
//-(CGFloat)positionOfDate_:(NSDate *)aDate;
-(NSDateComponents *)labelInterval_;
//-(NSDate *)dateAtPosition_:(CGFloat)xPosition;
-(ALTimelineCanvas *)canvas;
@end


#pragma mark -
@implementation ALTimelineScale

@synthesize timeline;
//@synthesize pointsPerDay;
@synthesize labels;
@synthesize needsLayout;
@synthesize addedLabels;
@synthesize removedLabels;
@synthesize visibleLabels;


+(void)initialize {
	ALTimelineScaleBackgroundColor = [NSColor colorWithCalibratedWhite:0.2f alpha:1.0f];
	ALTimelineScaleBorderColor = [NSColor colorWithCalibratedWhite:0.5f alpha:1.0f];
	NSColor *shadowColor = [NSColor colorWithCalibratedWhite:0.0f alpha:0.4f];
	ALTimelineScaleBorderShadow = [[NSShadow alloc] mcInitWithColor:shadowColor offset:NSMakeSize(0, -3) blurRadius:3];
}

-(void)viewDidMoveToWindow {
	NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
	if ([self window]) {
		[noteCenter addObserver:self selector:@selector(scaleFrameDidChange:) name:NSViewFrameDidChangeNotification object:self];
		NSScrollView *enclosingScrollView = [self enclosingScrollView];
		if (enclosingScrollView)
			[noteCenter addObserver:self selector:@selector(scaleDidScroll:) name:NSViewBoundsDidChangeNotification object:[enclosingScrollView contentView]];
	}
	else {
		[noteCenter removeObserver:self name:NSViewFrameDidChangeNotification object:nil];
		[noteCenter removeObserver:self name:NSViewBoundsDidChangeNotification object:nil];
	}
}


#pragma mark -
#pragma mark Accessors

-(void)setTimeline:(ALTimelineView *)aTimeline {
	timeline = aTimeline;
	AMBlockTask totalDaysTask = ^(id object, NSDictionary *change) {
		[self setNeedsLayout:YES];
	};
	[aTimeline addObserverForKeyPath:ALScaleTotalDaysKey task:totalDaysTask];
}

-(void)setNeedsLayout:(BOOL)shouldLayout {
	needsLayout = shouldLayout;
	if (needsLayout)
		[self setNeedsDisplay:YES];
}

-(ALTimelineCanvas *)canvas {
	return [[self timeline] canvas];
}


#pragma mark -
#pragma mark Notifications

-(void)scaleFrameDidChange:(NSNotification *)note {
	[self setNeedsLayout:YES];
}

-(void)scaleDidScroll:(NSNotification *)note {
	[self updateVisibleLabels_];
	for (ALTimelineScaleLabel *label in [self visibleLabels]) 
		[label setFrame:[label currentPosition]];
}


#pragma mark -
#pragma mark Drawing

-(void)viewWillDraw {
	if ([self needsLayout])
		[self layout];
	[super viewWillDraw];
}

-(void)drawRect:(NSRect)dirtyRect {
	[ALTimelineScaleBackgroundColor setFill];
	NSRect bounds = [self bounds];
	[NSBezierPath fillRect:bounds];
	[NSBezierPath setDefaultLineWidth:ALTimelineScaleBorderThickness];
	NSRect insetBounds = NSInsetRect(bounds, 0, ALTimelineScaleBorderThickness / 2);
	NSPoint topLeft = NSMakePoint(insetBounds.origin.x, NSMaxY(insetBounds));
	NSPoint topRight = NSMakePoint(NSMaxX(insetBounds), NSMaxY(insetBounds));
	[ALTimelineScaleBorderColor setStroke];
	[ALTimelineScaleBorderShadow set];
	[NSBezierPath strokeLineFromPoint:topLeft toPoint:topRight];
	[NSBezierPath setDefaultLineWidth:0];
}


#pragma mark -
#pragma mark Label layout

-(void)layout {
	[self setNeedsLayout:NO];
	NSArray *elements = [[self timeline] timelineElements];
	if ([elements count] == 0) {
		[self setSubviews:[NSArray array]];
		[self setLabels:nil];
		return;
	}
	NSRect bounds = [self bounds];
//	[self updatePointsPerDay];
	NSDateComponents *interval = [self labelInterval_];
	[ALTimelineScaleLabel setDateFormatWithInterval:interval];
	NSCalendar *calendar = [NSCalendar currentCalendar];
	NSDate *currentDate = [self firstLabelDateWithInterval_:interval];
	// When the interval gets down to a day or two, sometimes the first label date ends up being the same day as the first date which will be too close to the left edge
	if ([currentDate isEqualToDate:[[elements objectAtIndex:0] date]])
		currentDate = [calendar dateByAddingComponents:interval toDate:currentDate options:0];
	NSAssert(currentDate != nil, @"ALTimelineScale:layout - currentDate is nil");
	NSUInteger oldLabelCount = [[self labels] count];
	NSMutableArray *added = [NSMutableArray array];
	NSArray *removed = nil;
	NSMutableArray *newLabels = [NSMutableArray array];
	NSUInteger labelIndex = 0;
	for (; labelIndex < NSUIntegerMax; labelIndex++) {
		ALTimelineScaleLabel *label = nil;
		label = (labelIndex < oldLabelCount) ? [[self labels] objectAtIndex:labelIndex] : [[ALTimelineScaleLabel alloc] initWithFrame:NSZeroRect];
		[label setDate:currentDate];
		[self updatePositionOfLabel_:label];
		NSRect labelFrame = [label currentPosition];
		if (!NSContainsRect(bounds, labelFrame))
			break;
		[newLabels addObject:label];
		if (labelIndex >= oldLabelCount)
			[added addObject:label];
		currentDate = [calendar dateByAddingComponents:interval toDate:currentDate options:0];
	}
	if (labelIndex < oldLabelCount)
		removed = [[self labels] subarrayWithRange:NSMakeRange(labelIndex, oldLabelCount - labelIndex)];
	[self setAddedLabels:added];
	[self setRemovedLabels:removed];
	[self setLabels:newLabels];
	NSMutableArray *oldVisible = [[self visibleLabels] mutableCopy];
	[oldVisible removeObjectsInArray:removed];
	[self updateVisibleLabels_];
	[oldVisible removeObjectsInArray:[self visibleLabels]];
	[self positionOldVisibleLabels_:oldVisible];
	[self positionAddedLabels_];
	[self positionRemovedLabels_];
	[self positionVisibleLabels_];
}

-(void)updatePositionOfLabel_:(ALTimelineScaleLabel *)label {
	CGFloat scaleHeight = [self bounds].size.height;
	[label sizeToFitWidth];
	CGFloat tickPosition = [[self canvas] positionOfDate:[label date]];
	CGFloat tickOffset = [label tickPositionInRect:[label bounds]];
	CGFloat labelWidth = [label frame].size.width;
	NSRect labelRect = NSMakeRect(ceil(tickPosition - tickOffset), 0, labelWidth, scaleHeight);
	[label setOldPosition:[label currentPosition]];
	[label setCurrentPosition:labelRect];
}

-(void)updateVisibleLabels_ {
	[[self visibleLabels] makeObjectsPerformSelector:@selector(removeFromSuperview)];
	NSRect visibleRect = [self visibleRect];
	NSMutableArray *newVisible = [NSMutableArray array];
	for (ALTimelineScaleLabel *label in [self labels]) {
		NSRect labelRect = [label currentPosition];
		if (!NSIntersectsRect(labelRect, visibleRect))
			continue;
		[newVisible addObject:label];
		[self addSubview:label];
	}
	[self setVisibleLabels:newVisible];
}

-(void)positionOldVisibleLabels_:(NSArray *)oldLabels {
	for (ALTimelineScaleLabel *label in oldLabels) {
		[self addSubview:label];
		[[label animator] setFrame:[label currentPosition]];
	}
}

-(void)positionVisibleLabels_ {
	NSSet *addedSet = [NSSet setWithArray:[self addedLabels]];
	NSMutableSet *visibleSet = [NSMutableSet setWithArray:[self visibleLabels]];
	[visibleSet minusSet:addedSet];
	for (ALTimelineScaleLabel *label in visibleSet) {
		[[label animator] setFrame:[label currentPosition]];
	}
}

-(void)positionAddedLabels_ {
	for (ALTimelineScaleLabel *label in [self addedLabels]) {
		if (![[self visibleLabels] containsObject:label])
			continue;
		[label setFrame:[label currentPosition]];
		[label setAlphaValue:0];
		[self addSubview:label];
		[[label animator] setAlphaValue:1];
	}
}

-(void)positionRemovedLabels_ {
	for (ALTimelineScaleLabel *label in [self removedLabels]) {
		if ([label superview] != self)
			continue;
		CABasicAnimation *fadeOutAnimation = [CABasicAnimation animation];
		[fadeOutAnimation setDelegate:self];
		[fadeOutAnimation setValue:label forKey:ALTimelineScaleLabelToRemoveKey];
		[label setAnimations:[NSDictionary dictionaryWithObject:fadeOutAnimation forKey:ALScaleAlphaKey]];
		[[label animator] setAlphaValue:0];
	}
	[self setRemovedLabels:nil];
}

-(void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag {
	NSView *removeView = [animation valueForKey:ALTimelineScaleLabelToRemoveKey];
	if (removeView)
		[removeView removeFromSuperview];
}


#pragma mark -
#pragma mark Label creation

-(NSDate *)firstLabelDateWithInterval_:(NSDateComponents *)interval {
	CGFloat firstLabelPosition = ALTimelineScaleOptimumLabelSeparation / 2;
	
	NSDate *firstDate = [[self canvas] dateAtPosition:firstLabelPosition];
	if ([interval month] != NSUndefinedDateComponent)
		return [firstDate smrFirstOfNearestMonth];
	if ([interval year] != NSUndefinedDateComponent)
		return [firstDate smrFirstOfNearestYear];
	return firstDate;
}

-(CGFloat)pointsFromDate:(NSDate *)startDate toDate:(NSDate *)endDate {
	CGFloat startPoint = [[self canvas] positionOfDate:startDate];
	CGFloat endPoint = [[self canvas] positionOfDate:endDate];
	return endPoint - startPoint;
}

//-(NSDate *)dateAtPosition_:(CGFloat)xPosition {
//	NSArray *elements = [[self timeline] timelineElements];
//	if ([elements count] == 0)
//		return nil;
//	ALTimelineElement *firstElement = [elements objectAtIndex:0];
//	CGFloat firstX = [firstElement currentPosition].origin.x;
//	CGFloat deltaPoints = xPosition - firstX;
//	NSUInteger deltaDays = (NSUInteger) deltaPoints / [self pointsPerDay];
//	NSDateComponents *daysToAdd = [[NSDateComponents alloc] init];
//	[daysToAdd setDay:deltaDays];
//	NSDate *firstDate = [firstElement date];
//	NSCalendar *calendar = [NSCalendar currentCalendar];
//	NSDate *result = [calendar dateByAddingComponents:daysToAdd toDate:firstDate options:0];
//	return result;
//}

//-(CGFloat)positionOfDate_:(NSDate *)aDate {
//	NSArray *elements = [[self timeline] timelineElements];
//	if ([elements count] == 0)
//		return 0;
//	ALTimelineElement *firstElement = [elements objectAtIndex:0];
//	NSDate *firstDate = [firstElement date];
//	NSUInteger deltaDays = [aDate smrDaysAfterDate:firstDate];
//	CGFloat points = deltaDays * [self pointsPerDay];
//	CGFloat firstX = [firstElement currentPosition].origin.x;
//	return firstX + points;
//}

-(NSDateComponents *)labelInterval_ {
	NSDate *firstDate = [[self canvas] dateAtPosition:0];
	NSDate *secondDate = [[self canvas] dateAtPosition:ALTimelineScaleOptimumLabelSeparation];
	NSUInteger rawDays = [secondDate smrDaysAfterDate:firstDate];
	NSDateComponents *interval = [[NSDateComponents alloc] init];
	NSUInteger derivedDays = MAX(rawDays, 1u);
	if (derivedDays < 5)
		[interval setDay:derivedDays];
	else if (derivedDays >= 5 && derivedDays < 9)
		[interval setDay:7];
	else if (derivedDays >= 9 && derivedDays < 12)
		[interval setDay:10];
	else if (derivedDays >= 12 && derivedDays < 18)
		[interval setDay:14];
	else if (derivedDays >= 18 && derivedDays < 27)
		[interval setDay:21];
	else if (derivedDays >= 27 && derivedDays < 320) {
		NSUInteger months = round(derivedDays / 30);
		[interval setMonth:MAX(months, 1u)];
	}
	else {
		NSUInteger years = round(derivedDays / 365);
		[interval setYear:MAX(years, 1u)];
	}
	return interval;
}

//-(void)updatePointsPerDay {
//	NSArray *elements = [[self timeline] timelineElements];
//	if ([elements count] == 0)
//		return;
//	ALTimelineElement *firstElement = [elements objectAtIndex:0];
//	ALTimelineElement *lastElement = [elements lastObject];
//	CGFloat firstX = [firstElement currentPosition].origin.x;
//	CGFloat lastX = [lastElement currentPosition].origin.x;
//	NSUInteger days = [lastElement daysFromFirst];
//	[self setPointsPerDay:(lastX - firstX)/days];
//}

@end
